package xlimit

import (
	"errors"
	"sync"
	"sync/atomic"
)

var ErrRateLimited error = errors.New("rate limited")

// 基于滑动窗口的，并发安全的限流器。
type SlidingWindowRateLimiter struct {
	sw        *SlidingWindow // guarded by mu
	mu        *sync.Mutex    // guard sw
	threshold int64          // read/write through atomic operation
	timeMs    int64          // timeInterval
	cellCnt   int64          // cell数量
}

func NewSlidingWindowQpsRateLimiter(qpsThreshold int64) *SlidingWindowRateLimiter {
	return NewSlidingWindowRateLimiter(qpsThreshold, 1000, 10)
}

//ms必须是cellCnt的倍数，这样才够精确
// cellCnt不能过大，建议再[10,20]之间
//ms毫秒时间段内，最多有threshold个
func NewSlidingWindowRateLimiter(threshold int64, timeMs int64, cellCnt int64) *SlidingWindowRateLimiter {
	swrl := &SlidingWindowRateLimiter{
		// 滑动窗口覆盖 1s 时间，划分为 cellCnt 个 cell，每个 cell 时长为 timeMs/cellCnt  ms。
		sw:        NewSlidingWindow(cellCnt, timeMs/cellCnt),
		mu:        &sync.Mutex{},
		threshold: threshold,
		timeMs:    timeMs,
		cellCnt:   cellCnt,
	}
	return swrl
}

// 如果被限流，则返回 ErrRateLimited；未被限流，则返回 nil
func (swrl *SlidingWindowRateLimiter) Limit(metric string) error {
	nowMs := GetNowMs()
	threshold := atomic.LoadInt64(&swrl.threshold)
	timeMs := atomic.LoadInt64(&swrl.timeMs)

	swrl.mu.Lock()
	defer swrl.mu.Unlock()

	hits := swrl.sw.GetHit(nowMs, metric)
	actualDurationMs := swrl.sw.GetActualDurationMs(nowMs)
	//  hits/actualDurationMs  < threshold/timeMs
	// 将这个表达式改写成下面这个表达式
	if hits*timeMs >= threshold*actualDurationMs {
		return ErrRateLimited
	} else {
		swrl.sw.Hit(nowMs, metric)
		return nil
	}
}

func (swrl *SlidingWindowRateLimiter) ChangeThreshold(newThreshold int64, newTimeMs int64) {
	atomic.StoreInt64(&swrl.threshold, newThreshold)
	atomic.StoreInt64(&swrl.timeMs, newTimeMs)
}
func (swrl *SlidingWindowRateLimiter) ChangeQpsThreshold(newQpsThreshold int64) {
	atomic.StoreInt64(&swrl.threshold, newQpsThreshold)
	atomic.StoreInt64(&swrl.timeMs, 1000)
}
